package tasks;

import exceptions.SplitResponsibilitiesException;
import exceptions.TaskException;
import graph.adapters.IAdapter;
import graph.adapters.IJavaElementToIPackageFragmentCastAdapter;
import graph.dependencies.IDependency;
import graph.filters.IFilter;
import graph.filters.IJavaElementElementTypeFilter;
import graph.searchers.IJavaElementTreeClaimer;
import graph.searchers.Searcher;
import graph.visitors.DependencyCollector;
import graph.visitors.SimpleNameCollector;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.ui.text.correction.ASTResolving;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.UndoEdit;

public class SplitResponsibilitiesTask2 implements ITask {

	private boolean _isActive = false;
	
	private Set<Map<IMethodBinding, Set<ITypeBinding>>> _clusters = null;

	private TypeDeclaration _typeDeclaration = null;
	
	private List<ICompilationUnit> _iCompilationUnitsCreated = null;
	
	private CompilationUnit _parentCompilationUnit = null;
	
	private ICompilationUnit _iOldCompilationUnit = null;
	
	private String _oldCompilationUnitSource = null;
	
	private String _oldCompilationUnitName = null;

	private IPackageFragment _packageFragment = null;
	
	private static final String _taskName = "Split Class Responsibilities";	

	public SplitResponsibilitiesTask2(
			Set<Map<IMethodBinding, Set<ITypeBinding>>> clusters, TypeDeclaration typeDeclaration) {
		_isActive = true;
		_clusters  = clusters;
		_typeDeclaration = typeDeclaration;
		_iCompilationUnitsCreated = new LinkedList<ICompilationUnit>();
		_parentCompilationUnit = ASTResolving.findParentCompilationUnit(_typeDeclaration);
		_iOldCompilationUnit = (ICompilationUnit)_parentCompilationUnit.getJavaElement();
		try {
			_oldCompilationUnitSource = _iOldCompilationUnit.getSource();
		} catch (JavaModelException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		_oldCompilationUnitName = _iOldCompilationUnit.getElementName();
	}

	@Override
	public boolean isActive() {
		return _isActive ;
	}
	
	private void changeUnsplitted() {

	}
	
	private void createClass(IPackageFragment mypackage, String name, List<FieldDeclaration> fields, List<MethodDeclaration> methods) {
		Document document = new Document("");
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		parser.setSource(document.get().toCharArray());
		CompilationUnit compilationUnit = (CompilationUnit) parser.createAST(null);
		
		AST ast = compilationUnit.getAST();
		ASTRewrite rewriter = ASTRewrite.create(ast);
		ListRewrite lRW = rewriter.getListRewrite(compilationUnit, CompilationUnit.TYPES_PROPERTY);
		
		//Adding package declaration
		PackageDeclaration pD = ast.newPackageDeclaration();
		pD.setName(ast.newSimpleName(mypackage.getElementName()));
		lRW.insertLast(pD, null);
		
		for (Object a : _parentCompilationUnit.imports()) {
			ImportDeclaration importDeclaration = (ImportDeclaration)a;
			ASTNode copiedImportDeclaration = ASTNode.copySubtree(ast, importDeclaration);
			lRW.insertLast(copiedImportDeclaration, null);
		}
		
		//Creating class
		TypeDeclaration createdClass = ast.newTypeDeclaration();
		createdClass.setName(ast.newSimpleName(name));

		//Creating (copying) Fields 
		for (FieldDeclaration field : fields)
			createdClass.bodyDeclarations().add(ASTNode.copySubtree(createdClass.getAST(), field));
		
		//Creating (copying) Methods
		for (MethodDeclaration method : methods)
			createdClass.bodyDeclarations().add(ASTNode.copySubtree(createdClass.getAST(), method));

		lRW.insertLast(createdClass, null);
		TextEdit edits = rewriter.rewriteAST(document, null);
		try {
		    edits.apply(document);
		} catch(MalformedTreeException e) {
		    e.printStackTrace();
		} catch(BadLocationException e) {
		    e.printStackTrace();
		}
		try {
			_iCompilationUnitsCreated.add(mypackage.createCompilationUnit(name+".java", document.get(), true, null));
		} catch (JavaModelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			ResourcesPlugin.getWorkspace().save(true, null);
		} catch (CoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private List<FieldDeclaration> findFieldsUsedInMethod(TypeDeclaration typeDeclaration, IMethodBinding methodBinding) {
		MethodDeclaration currMethod = null;
		for (MethodDeclaration method : typeDeclaration.getMethods())
			if (method.resolveBinding().isEqualTo(methodBinding)) {
				currMethod = method;
				break;
			}
		SimpleNameCollector simpleNameCollector = new SimpleNameCollector();
		currMethod.accept(simpleNameCollector);
		List<SimpleName> simpleNames = simpleNameCollector.getSimpleNames();
		List<FieldDeclaration> fields = new LinkedList<FieldDeclaration>();
		for (SimpleName name : simpleNames) {
			IVariableBinding variableBinding = ASTNodes.getVariableBinding(name);
			if (variableBinding != null) {
				for (FieldDeclaration fieldDecl : typeDeclaration.getFields()){
					for (Object frag : fieldDecl.fragments()) {
						VariableDeclarationFragment varDeclFrag = (VariableDeclarationFragment)frag;
						if (varDeclFrag.resolveBinding().isEqualTo(variableBinding)) {
							boolean exists = false;
							for (FieldDeclaration fD : fields)
								if (fD.equals(fieldDecl)) {
									exists = true;
									break;
								}
							if (!exists)
								fields.add(fieldDecl);
						}
					}
				}
			}
		}
		return fields;
	}
	
	private IPackageFragment getParentNode(IJavaElement child, int elementType ) throws Exception {
		Searcher<Integer, IPackageFragment, IJavaElement> s = new Searcher<Integer, IPackageFragment, IJavaElement>();
		Iterator<IJavaElement> treeClaimer = new IJavaElementTreeClaimer(child);
		IFilter<Integer, IJavaElement> elementTypeFilter = new IJavaElementElementTypeFilter();
		elementTypeFilter.setEtalon(elementType);
		IAdapter<IPackageFragment, IJavaElement> caster = new IJavaElementToIPackageFragmentCastAdapter();
		List<IPackageFragment> searchResults = s.search(treeClaimer, elementTypeFilter, caster);
		if (searchResults.size()==1)
			return searchResults.get(0);
		else
			throw new Exception("Wrong number of found nodes");
	}

	@Override
	public void perform(Map<String,String> params) throws TaskException {
		String error = null;
		try {
		_packageFragment = getParentNode(_typeDeclaration.resolveBinding().getJavaElement(),
			IJavaElement.PACKAGE_FRAGMENT);
		} catch (Exception e) {
			error = "SplitResponsibilitiesTask>Unable to get parent node of type decalaration: "+e.getMessage();
			throw new SplitResponsibilitiesException(error);
		}
		String typeName = null;
		int clusterNum = 0;
		for (Map<IMethodBinding, Set<ITypeBinding>> cluster : _clusters) {
			List<MethodDeclaration> methods = new LinkedList<MethodDeclaration>();
			List<FieldDeclaration> fields = new LinkedList<FieldDeclaration>();
			for (Entry<IMethodBinding, Set<ITypeBinding>> entry : cluster.entrySet()) {
				if (typeName == null)
					typeName = entry.getKey().getDeclaringClass().getName();
				for (MethodDeclaration method : _typeDeclaration.getMethods())
					if (method.resolveBinding().isEqualTo(entry.getKey()))
						methods.add(method);
				List<FieldDeclaration> fieldsUsedInMethod = findFieldsUsedInMethod(_typeDeclaration, entry.getKey());
				
				System.out.println("FIELDS: "+fieldsUsedInMethod);
				
				for (FieldDeclaration field : fieldsUsedInMethod)
					if (!fields.contains(field/*fieldsUsedInMethod*/))
						fields.add(field);
			}
			System.out.println("CLASTER FIELDS: "+fields);
			
			List<String> clusterMethods = new LinkedList<String>();
			for (Entry<IMethodBinding, Set<ITypeBinding>> entry : cluster.entrySet())
				clusterMethods.add(entry.getKey().getName());
			createClass(_packageFragment, params.get(clusterMethods.toString())/*typeName+"_"+clusterNum*/, fields, methods);
			clusterNum++;
		}
		try {
			_iOldCompilationUnit.close();
			_iOldCompilationUnit.delete(true, null);
			_iOldCompilationUnit.discardWorkingCopy();
			if (_iOldCompilationUnit.exists())
				System.out.println("DELETED AND EXISTS");
			//_iOldCompilationUnit.reconcile(ICompilationUnit.NO_AST, true, true, _iOldCompilationUnit.getOwner(), null);
			//_iOldCompilationUnit.becomeWorkingCopy(null);
			//_iOldCompilationUnit.close();
			
		} catch (JavaModelException e) {
			error = "SplitResponsibilitiesTask>Unable to delete compilation unit: "+e.getMessage();
			throw new SplitResponsibilitiesException(error);
		}
		try {
			//for(IProject iP:ResourcesPlugin.getWorkspace().getRoot().getProjects())
				//iP.findMember(_iOldCompilationUnit.getPath(), true).delete(true, null);
			ResourcesPlugin.getWorkspace().save(true, null);
			ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.CLEAN_BUILD, null);
		} catch (CoreException e) {
			error = "SplitResponsibilitiesTask>Unable to save or update the workspace: "+e.getMessage();
			throw new SplitResponsibilitiesException(error);
		}
		_isActive = false;
		System.out.println("SplitResponsibilitiesTask>Completed");
	}

	@Override
	public void undo() {
		Document document = new Document(_oldCompilationUnitSource);
		try {
			_packageFragment.createCompilationUnit(_oldCompilationUnitName, document.get(), true, null);
		} catch (JavaModelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		for (ICompilationUnit iCU : _iCompilationUnitsCreated)
			try {
				iCU.delete(false, null);
			} catch (JavaModelException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}

	@Override
	public String getDescription() {
		return toString();
	}

	@Override
	public Error getLastError() {
		// TODO Auto-generated method stub
		return null;
	}
	
	private ICompilationUnit getIcompilationUnit() {
		IJavaElement iCompilationUnitOfSourceClass = _clusters.iterator().next().entrySet().iterator().next().getKey().getDeclaringClass().getJavaElement(); 
		while (iCompilationUnitOfSourceClass.getElementType() != IJavaElement.COMPILATION_UNIT)
			iCompilationUnitOfSourceClass = iCompilationUnitOfSourceClass.getParent();
		return (ICompilationUnit)iCompilationUnitOfSourceClass;
	}

	@Override
	public String getTargetModuleName() {
		return getIcompilationUnit().getElementName();
	}

	@Override
	public String toString() {
		String splitTaskDescription =  "Split method groups: ";
		for (Map<IMethodBinding, Set<ITypeBinding>> cluster : _clusters) {
			splitTaskDescription+= "[";
			for (Entry<IMethodBinding, Set<ITypeBinding>> entry : cluster.entrySet())
				splitTaskDescription+=entry.getKey().getName()+";";
			splitTaskDescription+= "] ";
		}
		splitTaskDescription+=" to different classes.";
		return splitTaskDescription;
	}

	@Override
	public Map<String, String> getParameters() {
		Map<String, String> paramsMap = new HashMap<String, String>();
		int i=0;
		for (Map<IMethodBinding, Set<ITypeBinding>> cluster : _clusters) {
			List<String> methods = new LinkedList<String>();
			for (Entry<IMethodBinding, Set<ITypeBinding>> entry : cluster.entrySet())
				methods.add(entry.getKey().getName());
			if (cluster.entrySet().iterator().hasNext()) {
				paramsMap.put(/*"Cluster: "+*/methods.toString()/*"cluster"+i*/, cluster.entrySet().iterator().next().getKey().getDeclaringClass().getName()+"_"+i);
				i++;
			}
		}
		return paramsMap;
	}

	@Override
	public String getTaskName() {
		return _taskName;
	}

	@Override
	public IPath getPath() {
		return getIcompilationUnit().getPath();
	}
}

